#include <os/sconfig.h>
#include <os/debug.h>
#include <os/environ.h>
#include <os/memcache.h>
#include <os/safety.h>
#include <os/kernelif.h>
#include <os/config.h>
#include <os/kernel.h>
#include <arch/x86.h>
#include <lib/stdio.h>
#include <lib/stdlib.h>
#include <lib/unistd.h>
#include <sys/fcntl.h>

LIST_HEAD(env_list_head);

#define ENV_FILE_PATH "/sys/env"

#define ENV_DEBUG

static void EnvBuild(env_t *env, char *buff)
{
    SConfigWrite(buff, env->name);
    SConfigWrite(buff, env->val);
    SConfigWriteLine(buff);
}

static int EnvSync()
{
    env_t *env;
    char buff[ENV_BUFF_LEN];

    int fp = KFileOpen(ENV_FILE_PATH, O_RDWR);
    if (fp < 0)
    {
        KPrint("[env] file open failed!\n");
        return -1;
    }
    // make all env data and write to file
    list_traversal_all_owner_to_next(env, &env_list_head, list)
    {
        memset(buff, 0, ENV_BUFF_LEN);
        EnvBuild(env, buff);
        if (KFileWrite(fp, buff, strlen(buff)) < 0)
        {
            KPrint("[env] when write sconfig data %s error!\n", buff);
            return -1;
        }
    }
    KFileWrite(fp, "\0", 1);
    KFileClose(fp);
    return 0;
}

static int EnvScanLine(char *buff)
{
    char name[ENV_NAME_LEN_MAX + 1];
    char val[ENV_VAL_LEN_MAX + 1];
    char *p = buff;

    if (!buff)
        return -1;

    p = SConfigRead(p, name, ENV_NAME_LEN_MAX);
    if (!p)
    {
        KPrint("[env] read env name failed!\n");
        return -1;
    }
    SConfigTrim(name);
    p = SConfigRead(p, val, ENV_VAL_LEN_MAX);
    if (!p)
    {
        KPrint("[env] read env value failed!\n");
        return -1;
    }
    SConfigTrim(val);
    return EnvPush(name, val, 0);
}

static int EnvLoadFile()
{
    char *buff, *p;
    char line[ENV_BUFF_LEN];
    status_t st;
    int size;

    int fp = KFileOpen(ENV_FILE_PATH, O_RDONLY);
    if (fp < 0)
    {
        KPrint("[env] open env file %s failed!\n", ENV_FILE_PATH);
        return -1;
    }
    // get file size
    KFileStatus(ENV_FILE_PATH, &st);
    size = st.st_size;

    buff = KMemAlloc(size);
    if (!buff)
    {
        KPrint("[env] alloc mem failed!\n");
        KFileClose(fp);
        return -1;
    }
    if (KFileRead(fp, buff, size) < 0)
    {
        KPrint("[env] read env file failed!\n");
        KMemFree(buff);
        KFileClose(fp);
        return -1;
    }

    // parse config line
    p = buff;
    while (1)
    {
        memset(line, 0, ENV_BUFF_LEN);
        p = SConfigReadLine(p, line, ENV_BUFF_LEN);
        if (!p)
            break;
        if (EnvScanLine(line) < 0)
        {
            return -1;
        }
    }
    // KMemFree(size);
    return 0;
}

static int EnvRead()
{
    int file_exist = 1;                       // defatult present
    if (KFileAccess(ENV_FILE_PATH, F_OK) < 0) // file no exist
    {
        KPrint("[env] create new file!\n");
        int fd = KFileOpen(ENV_FILE_PATH, O_CREATE | O_RDWR);
        if (fd < 0)
        {
            KPrint("[env] create env file %s failed!\n", ENV_FILE_PATH);
            return -1;
        }
        KFileClose(fd);
        file_exist = 0;
    }

    if (!file_exist) // file no exist
    {
        if (EnvPush("OS_NAME", KERNEL_NAME ",", 0) < 0)
            return -1;
        if (EnvPush("SYS_DIR", "/sys,", 0) < 0)
            return -1;
        if (EnvPush("PATH", "/bin,/sbin,/sysapp,/userapp,", 0) < 0)
            return -1;

        if (EnvSync() < 0)
        {
            KPrint("[env] sync env data failed!\n");
            return -1;
        }
    }
    else
    {
        if (EnvLoadFile() < 0)
        {
            KPrint("[env] load env data from file failed!\n");
            return -1;
        }
    }
    KPrint("read env data success!\n");
    return 0;
}

int EnvInit()
{
    list_init(&env_list_head);
    if (EnvRead() < 0)
    {
        KPrint("[env] read env failed!\n");
        return -1;
    }
    KPrint("[env] init environ done\n");
#ifdef ENV_DEBUG
    EnvDump();
#endif
    return 0;
}

int EnvPush(const char *name, const char *val, int overwrite)
{
    env_t *env = EnvFindByName(name);
    if (!env) // env no exist
    {
        env = (env_t *)KMemAlloc(sizeof(env_t));
        if (!env)
        {
            KPrint("[env] alloc mem failed!\n");
            return -1;
        }
        memcpy(env->name, name, strlen(name) + 1);
        memcpy(env->val, val, strlen(val) + 1);
        list_add_tail(&env->list, &env_list_head); // add to list
    }
    else
    {
        // env exist
        if (!overwrite)
            return -1;
        else
        {
            // update val string
            memset(env->val, 0, ENV_VAL_LEN_MAX + 1);
            memcpy(env->val, val, strlen(val) + 1);
            return 0;
        }
    }
    return 0;
}

int EnvPop(const char *name)
{
    env_t *env;

    list_traversal_all_owner_to_next(env, &env_list_head, list)
    {
        if (!strcmp(env->name, name))
        {
            list_del_init(&env->list);
            KMemFree(env);
            return 0;
        }
    }
    return -1;
}

void EnvPopAll()
{
    env_t *env;

    list_traversal_all_owner_to_next(env, &env_list_head, list)
    {
        list_del_init(&env->list);
        KMemFree(env);
    }
}

char *_env[32] = {NULL};
char env_data[32][64] = {0};

char **EnvTranslate(env_t *env)
{
    char *p = env->val;
    while (*p == ',') // skip front ',' character
        p++;
    int i = 0, j = 0;
    while (*p)
    {
        if (*p != ',')
            env_data[i][j++] = *p++;
        else
        {
            env_data[i][j] = '\0';
            p++;
            i++;
            j = 0;
        }
    }
    i = 0;
    while (env_data[i][0])
    {
        _env[i] = env_data[i];
        i++;
    }
    return _env;
}

env_t *EnvFindByName(const char *name)
{
    env_t *env = NULL;

    list_traversal_all_owner_to_next(env, &env_list_head, list)
    {
        if (!strcmp(env->name, name))
        {
            return env;
        }
    }
    return NULL;
}

char *EnvGet(const char *name)
{
    // KPrint("[env] EnvGet: %x %s\n",name,name);
    env_t *env = EnvFindByName(name);
    if (!env)
        return NULL;

    KPrint("[env] get env %s value %s\n", env->name, env->val);
    return env->val;
}

void EnvDump()
{
    env_t *env = NULL;
    list_traversal_all_owner_to_next(env, &env_list_head, list)
    {
        KPrint("env name %s env val %s\n", env->name, env->val);
    }
}

int EnvSet(const char *name, const char *value, int overwrite)
{
    if (EnvPush(name, value, overwrite) < 0)
        return -1;

    EnvSync();
    return 0;
}

int EnvUnset(const char *name)
{
    env_t *env = EnvFindByName(name);
    if (!env)
        return -1;

    // bakcup
    char temp_name[ENV_NAME_LEN_MAX + 1];
    char temp_val[ENV_VAL_LEN_MAX + 1];
    strcpy(temp_name, env->name);
    strcpy(temp_val, env->val);

    if (EnvPop(name) < 0)
        return -1;

    if (EnvSync() < 0)
    {
        // restore env data
        EnvPush(temp_name, temp_val, 0);
        return -1;
    }
    return 0;
}

int EnvClear()
{
    env_t *env;
    list_traversal_all_owner_to_next(env, &env_list_head, list)
    {
        list_del_init(&env->list);
        KMemFree(env);
    }
    return 0;
}

int SysEnvSet(const char *name, const char *val, int overwrite)
{
    return EnvSet(name, val, overwrite);
}

int SysEnvUnset(const char *name)
{
    return EnvUnset(name);
}

char *SysEnvGet(const char *name, const char *val, int vlen)
{
    if (!name)
        return NULL;
    char *tempval = EnvGet(name);
    if (!tempval)
        return NULL;
    if (val)
    {
        if (MemCopyToUser(val, tempval, vlen) < 0)
            return NULL;
        return val;
    }
    return NULL;
}

int SysEnvClear()
{
    return EnvClear();
}

int SysEnvPush(const char *name, const char *val, int overwrite)
{
    // KPrint("env push: name %s value %s",name,val);
    if (!name || !val)
        return -1;
    if (SafetyCheckRange(name, strlen(name)) < 0)
        return -1;
    if (SafetyCheckRange(val, strlen(val)) < 0)
        return -1;
    EnvPush(name, val, overwrite);
    return EnvSync();
}

env_t *SysEnv(env_t *p, env_t *out)
{
    env_t *env = NULL;

    if (SafetyCheckRange(p, sizeof(env_t)) < 0)
        return NULL;
    if (SafetyCheckRange(out, sizeof(env_t)) < 0)
        return NULL;

    // first called return first env
    if (!p)
        env = list_first_owner(&env_list_head, env_t, list);
    else
        env = list_next_owner(p, list); // return next env

    if (&env->list == &env_list_head)
        return NULL;

    // KPrint("[sysenv] env name %s val %s\n",env->name,env->val);

    if (env)
        MemCopyToUser(out, env, sizeof(env_t));
    else
        return NULL;

    return out;
}